package sceneObjects;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;

import utils.*;
import utils.Vector;
import main.Intersection;
import main.Parseable;
import main.Parser;
import main.RayTracer;

public class TriangularMeshes extends abstractSceneObject implements sceneObject, Parseable {
    private String filename;
    private Point3D pos;
    private double scale = 1.0;
    private String shader;

    private int numTriangles = 0;
    List<Triangle> triangles;
    Map<Point3D, Vector> db;

    @Override
    public boolean setParameter(String name, String[] args) {
	if (super.setParameter(name, args)) {
	    return true;
	} else if (name.equals("filename")) {
	    if (args.length != 1) {
		System.out.println("bad parameters");
		return false;
	    }
	    filename = args[0];
	    return true;
	} else if (name.equals("pos")) {
	    if (args.length != 3) {
		System.out.println("bad parameters");
		return false;
	    }
	    pos = new Point3D(Parser.stringArrToDoubleArr(args));
	    return true;
	} else if (name.equals("scale")) {
	    if (args.length != 1) {
		System.out.println("bad parameters");
		return false;
	    }
	    scale = Double.parseDouble(args[0]);
	    return true;
	} else if (name.equals("shader")) {
	    if (args.length != 1) {
		System.out.println("bad parameters");
		return false;
	    }
	    shader = args[0];
	    return true;
	}

	// We didn't find any parameter
	return false;
    }

    @Override
    public boolean finalizeObject() {
	if (!super.finalizeObject()) {
	    return false;
	}
	// Read mesh file
	if (filename == null || pos == null) {
	    return false;
	}

	if (!shader.equals("flat") && !shader.equals("phong")) {
	    shader = "phong";
	}

	int numPoints = 0;
	try {
	    FileReader confFile = new FileReader((RayTracer.getPathName() + "/" + filename));
	    BufferedReader bufRead = new BufferedReader(confFile);

	    String firstLine = bufRead.readLine();
	    if (firstLine.equals("OFF")) {
		String[] args = bufRead.readLine().split(" ");
		if (args.length < 2) {
		    return false;
		}
		numPoints = Integer.parseInt(args[0]);
		numTriangles = Integer.parseInt(args[1]);
	    } else {
		numPoints = Integer.parseInt(firstLine);
		numTriangles = Integer.parseInt(bufRead.readLine());
	    }

	    String[] curParams;
	    List<Point3D> points = new ArrayList<Point3D>();
	    Point3D p1, p2, p3;
	    for (int i = 0; i < numPoints; i++) {
		curParams = bufRead.readLine().split(" ");
		Point3D curPoint = new Point3D(Parser.stringArrToDoubleArr(curParams)).scale(scale).addPoint(pos);
		points.add(curPoint);
		db.put(curPoint, new Vector(0, 0, 0));
	    }
	    for (int i = 0; i < numTriangles; i++) {
		curParams = bufRead.readLine().split(" ");
		p1 = points.get(Integer.parseInt(curParams[1]));
		p2 = points.get(Integer.parseInt(curParams[2]));
		p3 = points.get(Integer.parseInt(curParams[3]));
		Triangle curTriangle = new Triangle(p1, p2, p3);
		triangles.add(curTriangle);
		db.put(p1, db.get(p1).add(curTriangle.getN0()));
		db.put(p2, db.get(p2).add(curTriangle.getN1()));
		db.put(p3, db.get(p3).add(curTriangle.getN2()));
	    }

	    bufRead.close();
	} catch (Exception e) {
	    System.out.println("Error reading configuration file: " + e.getMessage());
	    return false;
	}

	return true;
    }

    public TriangularMeshes() {
	triangles = new ArrayList<Triangle>();
	db = new HashMap<Point3D, Vector>();
	shader = "phong";
    }

    @Override
    public boolean isCamera() {
	return false;
    }

    public boolean isLight() {
	return false;
    }

    @Override
    public Intersection getIntersect(Ray ray) {

	Point3D minPoint = null;
	Point3D hitPoint = null;

	Intersection intersect;

	double distance = Double.POSITIVE_INFINITY;
	double tempDistance;
	int hitTriangle = 0;

	for (int i = 0; i < numTriangles; i++) {
	    intersect = triangles.get(i).getIntersect(ray);
	    if (intersect != null) {
		hitPoint = intersect.getPoint();
		if (hitPoint != null) {
		    tempDistance = ray.getSource().distance(hitPoint);
		    if (tempDistance < distance) {
			distance = tempDistance;
			minPoint = hitPoint;
			hitTriangle = i;
		    }
		}
	    }
	}

	if (minPoint == null) {
	    return null;
	} else {
	    return new Intersection(minPoint, this, hitTriangle);
	}

    }

    public Vector getNormalAtPoint(Intersection intersect) {
	Triangle t = triangles.get(intersect.getExtraData());
	if (shader.equals("flat")) {
	    return t.getNormal();
	} else {
	    Vector N0 = db.get(t.getP0()).normalize();
	    Vector N1 = db.get(t.getP1()).normalize();
	    Vector N2 = db.get(t.getP2()).normalize();

	    Point3D b = t.getBarycentric(intersect.getPoint());

	    return N0.scale(b.getX()).add(N1.scale(b.getY()).add(N2.scale(b.getZ()))).normalize();
	}
    }
}
